package cn.yanghan.service.impl;

import cn.yanghan.domain.KillCourse;
import cn.yanghan.domain.LoginUser;
import cn.yanghan.dto.KillDto;
import cn.yanghan.dto.KillOrderDto;
import cn.yanghan.interceptor.conText;
import cn.yanghan.mapper.KillCourseMapper;
import cn.yanghan.service.IKillCourseService;
import cn.yanghan.utils.exception.Assert;
import cn.yanghan.utils.exception.BusinessException;
import cn.yanghan.utils.util.StrUtils;
import com.baomidou.mybatisplus.mapper.EntityWrapper;
import com.baomidou.mybatisplus.mapper.Wrapper;
import com.baomidou.mybatisplus.service.impl.ServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.time.DateUtils;
import org.redisson.api.RLock;
import org.redisson.api.RSemaphore;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.BoundHashOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import javax.security.auth.login.LoginContext;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;

/**
 * <p>
 *  服务实现类
 * </p>
 *
 * @author yh
 * @since 2022-04-21
 */
@Service
@Slf4j
public class KillCourseServiceImpl extends ServiceImpl<KillCourseMapper, KillCourse> implements IKillCourseService {

    @Autowired
    private RedissonClient redissonClient;

    @Autowired
    private RedisTemplate redisTemplate;

    /**
     * 添加秒杀
     * 1.参数判断
     *
     * 3.保存数据到数据库
     * @param tKillCourse
     */
    @Override
    public void add(KillCourse tKillCourse) {

        //1.参数判断
        //2
        //3.保存数据到数据库
        tKillCourse.setKillSort(0);
        tKillCourse.setCreateTime(new Date());
        tKillCourse.setKillStatus(KillCourse.STATE_OFF);
        insert(tKillCourse);
    }

     /**
     * 发布课程到redis
     * 1.查询，待发布，近2天需要秒杀的
     * 2.把秒杀课程的库存放入redis
     * 3.状态修改成为已经上架，把秒杀课程放入redis
     * 4.修改数据库，状态修改为已经上架
     */
    @Override
    public void publicCourse2Redis() {

        //1.查询，待发布，近2天需要秒杀的
        Wrapper<KillCourse> wrapper = new EntityWrapper<>();
        wrapper.eq("kill_status",KillCourse.STATE_OFF);
        //近2天需要秒杀的
        Date now = new Date();
        Date endTime = DateUtils.addDays(now, 2);
        wrapper.between("start_time",now,endTime);
        List<KillCourse> killCourses = selectList(wrapper);
        if(killCourses.isEmpty())return;

        for (KillCourse killCourse : killCourses) {

            //加锁，防止2个线程同时进来，保证只有1个线程能进来
            RLock rLock = redissonClient.getLock("lock_kill_course:" + killCourse.getId());
            rLock.lock();
            log.info("加锁。。");
            try{
                //判断是否重复
                Boolean hasKey = redisTemplate.opsForHash().hasKey("KILL_COURSE", killCourse.getId().toString());
                if(hasKey){
                    log.warn("课程已经上架了 {}"+killCourse);
                    continue;
                }
                //2.把秒杀课程的库存放入redis,使用信号量
                Integer killStore = killCourse.getKillStore();
                RSemaphore semaphore = redissonClient.getSemaphore("store:" + killCourse.getId());
                boolean setPermitsSuccess = semaphore.trySetPermits(killStore);
                //如果成功
                if(setPermitsSuccess){
                    //3.状态修改成为已经上架，把秒杀课程放入redis
                    killCourse.setKillStatus(KillCourse.STATE_ON);
                    //如果有，不存，如果没有 存
                    //redisTemplate.opsForHash().putIfAbsent()
                    //设置一个秒杀码
                    String code = StrUtils.getComplexRandomString(4);
                    killCourse.setKillCode(code);
                    //把秒杀课程放入redis
                    redisTemplate.opsForHash().put("KILL_COURSE",killCourse.getId().toString(),killCourse);
                    //4.修改数据库状态，为已上架
                    updateById(killCourse);
                    log.info("课程成功发送到redis{}",killCourse);
                }
            }finally {
                log.info("解锁。。");
                if(rLock.isLocked())rLock.unlock();
            }


        }

    }

    /**
     * 查询秒杀列表
     * @return
     */
    @Override
    public List<KillCourse> selectListonline() {
        //通过key 查到value
        BoundHashOperations<Object,String,KillCourse> operations = redisTemplate.boundHashOps("KILL_COURSE");
        return operations.values();
    }

    /**
     * 查询单个秒杀
     * @return
     */
    @Override
    public KillCourse selectOneonline(Long id) {
        Assert.assertNotNull(id,"非法请求");
        return (KillCourse) redisTemplate.opsForHash().get("KILL_COURSE",id.toString());

    }

    /**
     * 秒杀
     * 参数：秒杀id，秒杀码
     *
     * @param dto
     * @return
     */
    SimpleDateFormat format = new SimpleDateFormat("yyyyMMddhhmmss");
    @Override
    public String kill(KillDto dto) {
        Long time = System.currentTimeMillis();
        //1.判断是否登录
        LoginUser login = conText.getLoginUser();
        login.setId(3L);
        Assert.assertNotNull(login,"请登录");
        //2.判断时间是否合法
        Long id = dto.getId();
        //根据秒杀id拿到秒杀课程
        KillCourse killCourse = (KillCourse) redisTemplate.opsForHash().get("KILL_COURSE", id.toString());
        Assert.assertNotNull(killCourse,"非法请求");
        Date now = new Date();
        if(now.before(killCourse.getStartTime()) || now.after(killCourse.getEndTime())){
            throw new BusinessException("下次时间早点");
        }
        //3.判断秒杀码使否一致
        if(!killCourse.getKillCode().equals(dto.getKillCode())){
            throw new BusinessException("秒杀码不一致");
        }
        //4.不能重复秒杀

        //5.尝试扣减信号量
        RSemaphore semaphore = redissonClient.getSemaphore("store:" + killCourse.getId());
        //秒杀几个
        boolean killSuccess = semaphore.tryAcquire(1);
        //6.扣减失败，手速慢了
        Assert.assertTrue(killSuccess,"手速慢了");

        //7.减扣成功，成功了
        //8.秒杀成功，构建订单数据，存储到redis
        String timeStr = this.format.format(now);
        String orderNo = timeStr+StrUtils.getRandomString(4);
        //欲创订单
        KillOrderDto killOrderDto = new KillOrderDto(
                login.getId(),
                orderNo,
                killCourse.getKillPrice(),
                now,
                killCourse.getId(),
                killCourse.getCourseId(),
                killCourse.getCourseName()
        );
        //订单号加用户id ，拼接
        String key = "killorder:"+orderNo+":"+login.getId();
        redisTemplate.opsForValue().set(key,killOrderDto);
        //9.返回订单号给用户
        log.info("秒杀耗时{}",System.currentTimeMillis()-time);
        return orderNo;
    }
}
